//#include <QtNetwork>
#include "networkclient.h"

#include <QDebug>
#include <QStringList>
#include <QDateTime>

#define DEBUG_OUTPUT 1

NetworkClient::NetworkClient(QObject *parent) :
    QThread(parent)
{
    unconnected = true;
    connected = false;
    loggedIn = false;
    waitingForLogs = false;
    waitingForUserList = false;
    waitingForMgmntFunction = false;

    user = NULL;
    serverAddress = NULL;
    socket = NULL;
}

/*======================================================
  Public Slots
  ======================================================*/
void NetworkClient::attemptLogIn(UserData * userData)
{
#if DEBUG_OUTPUT==1
    qDebug() << "running slot attemptLogIn";
    qDebug() << qPrintable(userData->getUserName());
    if (user != NULL)
        qDebug() << "user is not empty, deleting old user info";
#endif
    if (user != NULL)
        delete user;

    user = userData;
    userName = userData->getUserName();

    qDebug() << "Check for socket";
    // new connection socket
    if (!socket) {
#if DEBUG_OUTPUT==1
        qDebug() << "creating socket";
#endif
        socket = new QSslSocket(this);
        socket->setProtocol(QSsl::TlsV1);
        connect(socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
                this, SLOT(socketStateChanged(QAbstractSocket::SocketState)));
        connect(socket, SIGNAL(encrypted()),
                this, SLOT(socketEncrypted()));
        connect(socket, SIGNAL(sslErrors(QList<QSslError>)),
                this, SLOT(sslErrors(QList<QSslError>)));
        connect(socket, SIGNAL(readyRead()),
                this, SLOT(socketReadyRead()));
        connect(socket, SIGNAL(connected()), this, SLOT(socketConnected()));

        socket->setPeerVerifyMode(QSslSocket::QueryPeer);
    }

    if (unconnected)
    {
#if DEBUG_OUTPUT==1
        qDebug() << "create a encrypted connection on ssl server.";
#endif
        socket->connectToHost(*serverAddress, serverPort);
#if DEBUG_OUTPUT==1
        qDebug() << "socket trying to connect.";
#endif
        unconnected = false;
    }
    else if (!unconnected && !connected)
    {
        qDebug() << "We think we're attempting a connection already!";
        emit alreadyAttemptingConnection();
    }
}

void NetworkClient::attemptLogOut()
{
    if (!unconnected && connected && loggedIn) {
        socket->write("req:logout\n");
        socket->flush();
    }
}

// assumes address and port are valid
void NetworkClient::changeServerAddress(const QString &address, int port)
{
#if DEBUG_OUTPUT==1
    qDebug() << "attempting to change server address";
#endif

    if (!serverAddress)
    {
#if DEBUG_OUTPUT==1
    qDebug() << "no server address object, creating new QHostAddress";
#endif

        serverAddress = new QHostAddress(address);
    }
    else
    {
        serverAddress->setAddress(address);
    }

    serverPort = port;
#if DEBUG_OUTPUT==1
    qDebug() << "Server address successfully changed.";
#endif

}

void NetworkClient::sendNewLog(LogEntry *entry)
{
    qDebug() << "Trying to send new log";
    if(loggedIn && socket->isEncrypted())
    {
        QString packet("req:newlog;name:");
        qDebug() << "Getting name..";
        if (this->user == NULL) {
            qDebug() << "What's wrong with user?";
        }
        packet.append(userName); // TODO: should i get the username from LogEntry?
        packet.append(";log:");
        qDebug() << "Getting log data..";
        packet.append(entry->getLog());
        packet.append("\n");

        qDebug() << "Sending " + packet;
        socket->write(packet.toLatin1());
        socket->flush();
    }
}


void NetworkClient::requestLogs(QStringList *userNames)
{
    qDebug() << "About to request logs on users:";
    foreach(QString name, *userNames)
        qDebug() << name;
    if (connected && loggedIn && socket->isEncrypted() && !waitingForLogs) {
        waitingForLogs = true;
        QString packet("req:userLogs;name:");

        foreach(QString user, *userNames) {
            packet.append(user).append(',');
        }

        packet.remove(packet.length()-1, 1);
        qDebug() << "Removed, packet is now " + packet;

        packet.append("\n");

        socket->write(packet.toLatin1());
        socket->flush();
    }

    delete userNames;
}

void NetworkClient::requestAllUsers(UserData * un)
{
    qDebug() << "About to request access list";
    // server should automatically return a list of all users for admins or
    // a list of accessible users for normal users
    if (connected && loggedIn && socket->isEncrypted() && !waitingForUserList) {
        QString packet("req:accesslist;name:");
        packet.append(un->getUserName());
        packet.append("\n");

        qDebug() << "Sending " + packet;
        socket->write(packet.toLatin1());
        qDebug() <<"Dunno why but sending: " + packet;
        //waitingForUserList = true;
        socket->flush();
        waitingForUserList = true;
    }

    delete un;
}

void NetworkClient::newUserRequest(UserData *data)
{
    qDebug() << "New user to add: " + data->getUserName() + data->getHashedPassword();
    if (waitingForMgmntFunction)
        qDebug() << "waiting for some other management function to finish!";
    /**
      Add New User Request packet format:
        Section delimiter:  ;
        Header delimiter:   :
        sections    headers     contents
            0       "req"       "addUser"
            1       "name"      <userName>
            2       "pw"        <hashedPassword>
            3       "privilege" <0=user,1=admin>
      */
    if (loggedIn && !waitingForMgmntFunction) {
        QString packet = "req:addUser";
        packet.append(";name:").append(data->getUserName());
        packet.append(";pw:").append(data->getHashedPassword());
        packet.append(";privilege:");
        if (data->isAdmin())
            packet.append("1\n");
        else
            packet.append("0\n");
        qDebug() << "packet: " + packet;
        socket->write(packet.toLatin1());
        socket->flush();
        waitingForMgmntFunction = true;
    }
}

void NetworkClient::removeUserRequest(UserData *data)
{
    /**
      Remove Existing User Request packet format:
        Section delimiter:  ;
        Header delimiter:   :
        sections    headers     contents
            0       "req"       "removeUser"
            1       "name"      <userName>
            2       "pw"        <hashedPassword>
            3       "privilege" <0=user,1=admin>
      */
    if (loggedIn && !waitingForMgmntFunction) {
        QString packet = "req:removeUser";
        packet.append(";name:").append(data->getUserName()).append("\n");

        socket->write(packet.toLatin1());
        socket->flush();
        waitingForMgmntFunction = true;
    }
}

void NetworkClient::addAccessRequest(const QString &name, const QString &target)
{
    /**
      Add User Accessibility Request packet format:
        Section delimiter:  ;
        Header delimiter:   :
        sections    headers     contents
            0       "req"       "addAccess"
            1       "name"      <userName>
            2       "target"    <targetUserName>
      */
    if (loggedIn && !waitingForMgmntFunction) {
        QString packet = "req:addAccess";
        packet.append(";name:").append(name);
        packet.append(";target:").append(target).append("\n");

        qDebug() << "addAccessReqest packet: " + packet;
        socket->write(packet.toLatin1());
        socket->flush();
        waitingForMgmntFunction = true;
    }
}

void NetworkClient::removeAccessRequest(const QString &name, const QString &target)
{
    /**
      Remove User Accessibility Request packet format:
        Section delimiter:  ;
        Header delimiter:   :
        sections    headers     contents
            0       "req"       "removeAccess"
            1       "name"      <userName>
            2       "target"    <targetUserName>
      */
    if (loggedIn && !waitingForMgmntFunction) {
        QString packet = "req:removeAccess";
        packet.append(";name:").append(name);
        packet.append(";target:").append(target).append("\n");

        socket->write(packet.toLatin1());
        socket->flush();
        waitingForMgmntFunction = true;
    }
}

/*======================================================
  Private Slots
  ======================================================*/
void NetworkClient::socketStateChanged(QAbstractSocket::SocketState state)
{
    qWarning("Socket state changed: %s", state==0?"Unconnected":state==1?"HostLookup":state==2?"Connecting":state==3?"Connected":
                                         state==4?"Bound":state==6?"Closing":state==5?"Listening":"Unknown");
    if (state == QAbstractSocket::UnconnectedState) {
        unconnected = true;
        connected = false;
        socket->deleteLater();
        socket = 0;
    }
}

void NetworkClient::sslErrors(QList<QSslError> errList)
{
    foreach (QSslError err, errList) {
        emit sslError(err.errorString());
    }
}

void NetworkClient::socketConnected()
{
    socket->startClientEncryption();
}

void NetworkClient::socketEncrypted()
{
    // allow client to know when disconnection happens
    connect(socket, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
            this, SLOT(socketStateChanged(QAbstractSocket::SocketState)));
    qDebug() << "connected ";
    connected = true;
    if (!loggedIn) {
        qDebug() << "sending login info";
        // send data and check.
        if (!user)
            qDebug() << "no user ready!";
        else
        {
            qDebug() << qPrintable(user->getUserName());
            qDebug() << qPrintable(user->getHashedPassword());
        }
        QString packet("req:login;name:");
        packet.append(user->getUserName());
        packet.append(";pw:");
        packet.append(user->getHashedPassword());
        packet.append("\n");
        qDebug() << "String constructed.  Sending packet over socket.";
        socket->write(packet.toLatin1());
        socket->flush();
        qDebug() << "sent login info";
    }
}

void NetworkClient::socketReadyRead()
{
    qDebug() << "Ready to read";
    // wait until a full line can be read from socket
    if (socket->canReadLine())
    {
        if (!loggedIn)
        {
            qDebug() << "We're not logged in yet.";
            checkLoginReply();
        }
        else if (waitingForLogs)
        {
            qDebug() << "We've request and are waiting for logs";
            readLogReply();
        }
        else if (waitingForUserList)
        {
            qDebug() << "We're waiting for a list of users";
            readUserList();
        }
        else if (waitingForMgmntFunction)
        {
            qDebug () << "expecting some management reply from server";
            checkMgmntReply();
        }
        else
        {
            qDebug() << "Some other shit yo!";
            checkReply();
        }
    }
}

/*======================================================
  Private Functions
  ======================================================*/
void NetworkClient::badReply(const QString &expectedSection, const QString & badSection)
{
    QString errMsg("Bad reply from server. Expected: ");
    errMsg.append(expectedSection);
    errMsg.append("\nReceived: ").append(badSection);
    if (!loggedIn)
        emit logInFailed(errMsg);
    else
        emit badReplyReceived(errMsg);
}

void NetworkClient::checkLoginReply()
{
    qDebug() << "Checking log-in reply";
    QString packet = socket->readLine().trimmed();

    qWarning("%s received", qPrintable(packet));
    QStringList reply = packet.split(';',QString::SkipEmptyParts);

    QStringList entry = reply.first().split(':');

    // Check reply from server
    if (entry.first() == "login" && entry.last() == "success")
    {
        loggedIn = true;

        reply.removeFirst();
        entry = reply.first().split(':');

        if (entry.first() == "privilege" && entry.last() == "admin")
            user->setPrivilege(1);
        else if (entry.first() == "privilege" && entry.last() == "user")
            user->setPrivilege(0);
        else
        {
            badReply("User privilege level", reply.first());
            return;
        }
        emit logInSuccessful(user);
    }
    else if (entry.first() == "login" && entry.last() == "failed")
    {
        reply.removeFirst();
        entry = reply.first().split(':');

        if (entry.first() == "failureReason")
        {
            qWarning("Login failed: %s", qPrintable(reply.last()));
            emit logInFailed(entry.last());
        }
        else
            badReply("Reason log-in failed", reply.first());

        qDebug() << "Disconnecting socket..";
        socket->disconnectFromHost();

        user->deleteLater();
        user = NULL;
    }
    else
    {
        badReply("Log-in success/failure", reply.first());
    }
}

// networked log format: "logEntry#;#<timeStamp>#:#<owner>#:#<log>\n"
// if line starts with "endLogs", finish reading logs from server.
void NetworkClient::readLogReply()
{
    qDebug() <<"readLogReply...";
    // keep reading logs
    while (socket->canReadLine() && waitingForLogs)
    {
        qDebug() << "reading lines..";
        QString packet(socket->readLine().trimmed());

        qDebug() << "packet: " << packet;
        if (packet.section("#;#", 0, 0) == "endLogs")
        {
            qDebug() <<"end logs packet received";
            waitingForLogs = false;
            break;
        }
        else if (packet.section("#;#", 0, 0) == "logEntry")
        {
            LogEntry *log = new LogEntry();

            QString entries = packet.section("#;#", 1);

            qDebug() << "entries: " << entries;
            log->setTimeStamp(QDateTime::fromString(entries.section("#:#",0,0),"yyyy-MM-dd hh:mm:ss"));
            log->setOwner(entries.section("#:#",1,1));
            log->setLog(entries.section("#:#",2,2));

            qDebug() << "logReadyToRead sent out" << "time: " << log->getTimeStamp().toString();
            emit logReadyToRead(log);
        }
    }
}

void NetworkClient::readUserList()
{
    qDebug() << "Reading user list:";
    QString packet(socket->readLine().trimmed());

    qDebug() << "Received: " + packet;
    QStringList line = packet.split(';');

    if (!line.empty() && line.first() == "accessList")
    {
        waitingForUserList = false;
        QStringList *accessList = new QStringList();
        line.removeFirst();

        // parse user's access list
        for (line; !line.empty(); line.removeFirst()) {
            QStringList entry = line.first().split(':');

            // check for transmission error
            if (entry.first() != "uname")
            {
                delete accessList;
                emit userListError("Server reply in unexpected format.");
                return;
            }

            accessList->append(entry.last());
        }

        emit userListReady(accessList);
    }
    else
    {
        emit userListError("Server reply in unexpected format.");
    }
}

void NetworkClient::checkMgmntReply()
{
    QString packet(socket->readLine().trimmed());
    QString response = packet.section(';', 0, 0);

    qDebug() << "Received: " + packet;
    if(response.section(':', 0, 0) == "newUserAdded") {
        qDebug() << "in newUserAdded";
        if (response.section(':', 1, 1) == "success") {
            QString userName = packet.section(';', 1);
            qDebug() << "Emitting newUserAdded(" + userName + ")";
            emit newUserAdded(userName);
        }
        else if (response.section(':', 1, 1) == "failed") {
            QString failReason = packet.section(';', 1);
            emit newUserNotAdded(failReason);
        }
        else
            badReply("Add user response", response);

        waitingForMgmntFunction = false;
    }
    else if (response.section(':', 0, 0) == "userRemoved") {
        qDebug() << "in userRemoved";
        if (response.section(':', 1, 1) == "success") {
            qDebug() << "Emmitting userRemoved()";
            emit userRemoved();
        }
        else if (response.section(':', 1, 1) == "failed") {
            QString failReason = packet.section(';', 1);
            emit userRemoveFailed(failReason);
        }
        else
            badReply("Remove user response", response);

        waitingForMgmntFunction = false;
    }
    else if (response.section(':', 0, 0) == "accessAdded") {
        qDebug() << "in accessAdded";
        if (response.section(':', 1, 1) == "success") {
            emit accessAddSuccess();
        }
        else if (response.section(':', 1, 1) == "failed") {
            QString failReason = packet.section(';', 1);
            emit accessAddFailed(failReason);
        }
        else
            badReply("Access add response", response);

        waitingForMgmntFunction = false;
    }
    else if (response.section(':', 0, 0) == "accessRemoved") {
        qDebug() << "in accessRemoved";
        if (response.section(':', 1, 1) == "success") {
            emit accessRemoveSuccess();
        }
        else if (response.section(':', 1, 1) == "failed") {
            QString failReason = packet.section(';', 1);
            emit accessRemoveFailed(failReason);
        }
        else
            badReply("Access remove response", response);

        waitingForMgmntFunction = false;
    }
}

void NetworkClient::checkReply()
{
    QString packet = socket->readLine().trimmed();

    qDebug() << "Received: " + packet;
    qDebug() << "Expecting \"newLogSuccess\": " + packet.section(';',0,0);
    qDebug() << "Expecting timestamp: " + packet.section(';',1);

    if (packet.section(';',0,0) == "logout")
        emit logOutSuccess();
    else if (packet.section(';',0,0) == "newLogSuccess")
        emit newLogEntrySuccessful(packet.section(';',1));
    else if (packet.section(';',0,0) == "newLogFailure")
        emit newLogEntryError("Your log could not be recorded.");
}
